<!DOCTYPE html>
<html lang="zh-cn">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">




<title>前端性能优化 | Miles`s</title>

<link rel="stylesheet" href="https://mada.gitee.io/notes//css/styles.css">

<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css" 
integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>


<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/zenburn.min.css" crossorigin="anonymous" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js" integrity="sha256-/BfiIkHlHoVihZdc6TFuj7MmJ0TWcWsMXkeDFwhi0zw=" crossorigin="anonymous"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script src="https://mada.gitee.io/notes//js/highlight.js"></script>






<div class="container">
    <nav class="navbar level">
      <div class="navbar-brand">
          <a class="nav-item" href="https://mada.gitee.io/notes/"><h1 class="title is-3">Miles`s</h1></a>
      </div>           
      <div class="navbar-menu has-text-centered is-active">
          <div class="navbar-end is-centered">
              
           </div>
      </div>
    </nav>
  </div>
<div class="container">
  <h2 class="subtitle is-6">November 7, 2016</h2>
  <h1 class="subtitle is-size-4-mobile is-size-3-desktop">前端性能优化</h1>
  <div class="content">
    

<h1 id="优化">优化</h1>

<blockquote>
<p>程序员们浪费了大量时间来思考，或者说是担忧，他们的程序中非关键部分的运行速
度。并且他们对于性能的这些尝试，实际上却对代码的调试和维护有着非常消极的影
响。我们应当忘记那些不重要的性能影响，在 97% 的时间里都可以这么说：过早优
化乃万恶之源。当然我们也不应当在那关键的 3% 上放弃我们的机会。—— Donald
Knuth</p>
</blockquote>

<h2 id="资料">资料</h2>

<p><a href="http://taobaofed.org/blog/2016/04/05/optimize-in-tbhome/">淘宝首页性能优化实践</a></p>

<p><a href="http://taobaofed.org/blog/2015/12/16/h5-performance-optimization-and-domain-convergence/">无线性能优化：域名收敛</a></p>

<p><a href="http://www.jianshu.com/p/49d879699a67">移动端性能优化</a>
<img src="../../images/performance/mobile.png" alt="移动端性能优化" /></p>

<p><a href="https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/">浏览器的工作原理：新式网络浏览器幕后揭秘</a></p>

<p><a href="http://taligarsiel.com/Projects/howbrowserswork1.htm#Introduction">How browers work</a></p>

<h2 id="页面加载">页面加载</h2>

<h3 id="浏览器下载">浏览器下载</h3>

<p>chrome：</p>

<ul>
<li>并行加载最多6个资源。</li>
<li>浏览器优先下载css文件。</li>
<li>ie8以后以及现代浏览器会预解析，提前下载文档中的外部脚本，并不影响主进程下载和渲染。</li>
</ul>

<h4 id="脚本解析-parsing-scripts">脚本解析 Parsing scripts</h4>

<p><strong>脚本</strong></p>

<p>web的模式是同步的，开发者希望解析到一个script标签时立即解析执行脚本，并阻塞文档的解析直到脚本执行完。如果脚本是外引的，则网络必须先请求到这个资源——这个过程也是同步的，会阻塞文档的解析直到资源被请求到。这个模式保持了很多年，并且在html4及html5中都特别指定了。开发者可以将脚本标识为defer，以使其不阻塞文档解析，并在文档解析结束后执行。Html5增加了标记脚本为异步的选项，以使脚本的解析执行使用另一个线程。</p>

<p><strong>预解析 Speculative parsing</strong></p>

<p>Webkit和Firefox都做了这个优化，当执行脚本时，另一个线程解析剩下的文档，并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载从而使整体速度更快。需要注意的是，预解析并不改变Dom树，它将这个工作留给主解析过程，自己只解析外部资源的引用，比如外部脚本、样式表及图片。</p>

<p><strong>样式表 Style sheets</strong></p>

<p>样式表采用另一种不同的模式。理论上，既然样式表不改变Dom树，也就没有必要停下文档的解析等待它们，然而，存在一个问题，脚本可能在文档的解析过程中请求样式信息，如果样式还没有加载和解析，脚本将得到错误的值，显然这将会导致很多问题，这看起来是个边缘情况，但确实很常见。Firefox在存在样式表还在加载和解析时阻塞所有的脚本，而Chrome只在当脚本试图访问某些可能被未加载的样式表所影响的特定的样式属性时才阻塞这些脚本。</p>

<p><strong>渲染树构建（Render tree construction）</strong></p>

<p>当Dom树构建完成时，浏览器开始构建另一棵树——渲染树。渲染树由元素显示序列中的可见元素组成，它是文档的可视化表示，构建这棵树是为了以正确的顺序绘制文档内容。
　　Firefox将渲染树中的元素称为frames，WebKit则用renderer或渲染对象来描述这些元素。
　　一个渲染对象知道怎么布局及绘制自己及它的children。
　　
　　每个渲染对象用一个和该节点的css盒模型相对应的矩形区域来表示，正如css2所描述的那样，它包含诸如宽、高和位置之类的几何信息。盒模型的类型受该节点相关的display样式属性的影响（参考样式计算章节）。下面的webkit代码说明了如何根据display属性决定某个节点创建何种类型的渲染对象。</p>

<p><strong>渲染树和Dom树的关系（The render tree relation to the DOM tree）</strong></p>

<p>渲染对象和Dom元素相对应，但这种对应关系不是一对一的，不可见的Dom元素不会被插入渲染树，例如head元素。另外，display属性为none的元素也不会在渲染树中出现（visibility属性为hidden的元素将出现在渲染树中）。
　　还有一些Dom元素对应几个可见对象，它们一般是一些具有复杂结构的元素，无法用一个矩形来描述。例如，select元素有三个渲染对象——一个显示区域、一个下拉列表及一个按钮。同样，当文本因为宽度不够而折行时，新行将作为额外的渲染元素被添加。另一个多个渲染对象的例子是不规范的html，根据css规范，一个行内元素只能仅包含行内元素或仅包含块状元素，在存在混合内容时，将会创建匿名的块状渲染对象包裹住行内元素。
　　一些渲染对象和所对应的Dom节点不在树上相同的位置，例如，浮动和绝对定位的元素在文本流之外，在两棵树上的位置不同，渲染树上标识出真实的结构，并用一个占位结构标识出它们原来的位置。
<img src="../../images/performance/render-tree.png" alt="render tree" /></p>

<h3 id="页面可见时间与异步加载">页面可见时间与异步加载</h3>

<h4 id="页面可见时间">页面可见时间</h4>

<p>页面可见要经历的过程：</p>

<ol>
<li>解析<code>HTML</code>为<code>DOM</code>，解析<code>CSS</code>为<code>CSSOM</code>（css object model）</li>
<li>将<code>DOM</code>和<code>CSSOM</code>合成一颗渲染树（render tree）</li>
<li>完成渲染树的布局（Layout）</li>
<li>将渲染树绘制到屏幕</li>
</ol>

<p><img src="../../notes/images/performance/dom-cssom-rendertree-layout.jpg" alt="渲染" /></p>

<p>js的下载以及执行会阻塞DOM与CSSOM的下载与构建，因此采用如下几种策略降低js阻塞DOM和CSSOM的构建，缩短首屏可见的时间。</p>

<h4 id="bottom-script">Bottom Script</h4>

<p>常规的优化策略，js放在body的底部，优先加载html和css</p>

<p>缺点：</p>

<p>iOS Safari 、Android browser 和 iOS WebView 表现一致，即使 script 放在 body 的底部也无济于事，页面白屏时间长。</p>

<p>Android WebView 的表现和 PC 的 Chrome 一致，优先加载DOM和CSSOM。</p>

<h4 id="document-write">document.write</h4>

<p>没有异步效果，与bottom script 的情况一致。</p>

<pre><code class="language-js">function injectWrite(src){
  document.write('&lt;script src=&quot;' + src + '&quot;&gt;&lt;/sc' + 'ript&gt;');
}
</code></pre>

<h4 id="getscript">getScript</h4>

<pre><code class="language-js">var script = document.createElement('script');
script.src = &quot;//g.tbcdn.com/xx.js&quot;;
document.getElementsByTagName('head')[0].appendChild(script);
</code></pre>

<h4 id="async-属性">async 属性</h4>

<p>HTML 5 规范，其作用是，使用另一个进程下载脚本，下载时不会阻塞渲染，并且下载完成后立刻执行。</p>

<pre><code class="language-js">&lt;script src=&quot;app.js&quot; async&gt;&lt;/script&gt;
</code></pre>

<blockquote>
<ul>
<li>浏览器开始解析HTML网页</li>
<li>解析过程中，发现带有async属性的script标签</li>
<li>浏览器继续往下解析HTML网页，解析完先显示网页并触发<code>DOMContentLoaded</code>，同时并行下载script标签中的外部脚本
脚本下载完成，浏览器暂停解析HTML网页，开始执行下载的脚本
脚本执行完毕，浏览器恢复解析HTML网页</li>
</ul>
</blockquote>

<p>async 属性可以保证脚本下载的同时，浏览器继续渲染。但是 async 无法保证脚本的执行顺序。哪个脚本先下载结束，就先执行那个脚本。</p>

<h4 id="defer-属性">defer 属性</h4>

<p>没有异步效果，与bottom script 的情况一致。
HTML 4.0 规范，其作用是，告诉浏览器，等到 DOM+CSSOM 渲染完成，再执行指定脚本。</p>

<pre><code class="language-js">&lt;script defer src=&quot;app.js&quot; &gt;&lt;/script&gt;
</code></pre>

<blockquote>
<p>＊ 浏览器开始解析HTML网页
＊ 解析过程中，发现带有defer属性的script标签
＊ 浏览器继续向下解析HTML网页，解析完就渲染到页面上，同时并行下载script标签中的外部脚本
＊ 浏览器完成解析HTML网页，此时再执行下载的脚本，完成后触发<code>DOMContentLoaded</code></p>
</blockquote>

<p>下载的脚本文件在 DOMContentLoaded 事件触发前执行（即刚刚读取完&lt;\/html&gt;标签），而且可以保证执行顺序就是它们在页面上出现的顺序。所以 添加 defer 属性后，domReady 的时间并没有提前，但它可以让页面更快显示出来。</p>

<p>将放在页面上方的 script 加 defer，在 PC Chrome 下其效果相当于 把这个 script 放在底部，页面会先显示。 但对 iOS Safari 和 iOS WebView 加 defer 和 script 放底部一样都是长时间白屏。</p>

<h4 id="如何选择-async-和-defer">如何选择 async 和 defer</h4>

<ul>
<li>defer 可以保证执行顺序，async 不行【注：&lt;=IE 9 defer 执行顺序有 bug，但可以 <a href="https://github.com/h5bp/lazyweb-requests/issues/42">hack</a>】</li>
<li>async 可以提前触发 domReady，defer 不行【注：Firefox 的 defer 也可以提前触发 domready】</li>
<li>defer 在 iOS 和部分 Android 下依然阻塞渲染，白屏时间长。</li>
<li>当 script 同时加 async 和 defer 属性时，后者不起作用，浏览器行为由 async 属性决定。</li>
<li>async 和 defer 的兼容性不一致，好在 async 和 defer 无线端基本都支持，async 不支持 IE 9-。</li>
</ul>

<p>综合上面 async 和 defer，推荐以下用法。</p>

<pre><code class="language-js">&lt;!-- 现代浏览器用 'async', ie9-用 'defer' --&gt;
&lt;script src=&quot;//g.alicdn.com/alilog/mlog/aplus_wap.js&quot; async defer&gt;&lt;/script&gt;
</code></pre>

<h4 id="最后建议">最后建议</h4>

<ul>
<li>业务 JS 尽量异步，放 body 底部的 JS 在 iOS 上和部分 Android 是无效的，依然会阻塞首屏渲染。</li>
<li>异步的方式尽可能原生用async，容器（浏览器、webview 等）级别自带优化，不要通过 JS 去模拟实现，如 getScript/ajax/KISSY.use/$.use 等。</li>
<li>有顺序依赖关系的 JS 可以加 defer，不改变执行顺序，相当于放到页面底部，如 TMS head 中一时无法挪动位置的类库等。</li>
</ul>

<p>遗留问题：</p>

<ol>
<li>ios上和部分android defer无效，并且放在底部无效，并且脚本有顺序依赖如何优化？</li>
</ol>

<h3 id="补充">补充</h3>

<h4 id="didfinishload">didFinishLoad</h4>

<p><code>didFinisheLoad</code> 事件的触发和 JS 中的 <code>domReady</code>（DOMContentLoaded）和 <code>onLoad</code> 触发没有任何关联。</p>

<p>那它到底是什么时候触发呢？ <a href="https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIWebViewDelegate_Protocol/#//apple_ref/occ/intfm/UIWebViewDelegate/webViewDidFinishLoad:">iOS 官方文档</a> 是 Sent after a web view finishes loading a frame。 didFinishLoad 是在连续发起的请求结束之后触发，监听一段时间内无请求则触发。</p>

<p>因此在 JS 中用 setTimeout 来延迟发送请求也有可能会影响 didFinishLoad 的时间，建议 setTimeout 的时间设置得更长一点，如 3s。</p>

<h4 id="domcontentloaded">DOMContentLoaded</h4>

<p>页面文档完全加载并解析完毕之后,会触发DOMContentLoaded事件，HTML文档不会等待样式文件,图片文件,子框架页面的加载(load事件可以用来检测HTML页面是否完全加载完毕(fully-loaded))。</p>

<p>Notes:</p>

<ol>
<li>样式文件的加载会阻塞脚本的执行,所以如果你把一个<code>&lt;script&gt;</code>元素放在了一个<code>&lt;link rel=&quot;stylesheet&quot;&gt;</code>后面,如果页面没有完成解析,则DOMContentLoaded事件不会触发,直到样式文件加载完成之后.</li>
<li>在IE8中,可以使用readystatechange事件来检测DOM文档是否加载完毕</li>
</ol>

<p>诸如jQuery这样的通用JS库，会提供跨浏览器的方法来检测DOM是否加载完成。也有其他专门实现该功能的脚本:<a href="https://github.com/dperini/ContentLoaded/blob/master/src/contentloaded.js">contentloaded.js</a> (只能添一个时间监听函数)以及<a href="https://github.com/addyosmani/jquery.parts/blob/master/jquery.documentReady.js">jquery.documentReady.js</a> (并不依赖jQuery,虽然名字中有jquery).</p>

<h4 id="load">load</h4>

<p>load事件是在当一个资源和与其依赖资源加载完成后触发的。</p>

<p><a href="http://stackoverflow.com/questions/21204612/javascript-load-vs-ready-vs-domready-vs-domcontentloaded-events">有待整理</a>
<a href="http://javascript.info/tutorial/onload-ondomcontentloaded">onLoad and onDOMContentLoaded</a></p>

<p>From ：
1. <a href="http://taobaofed.org/blog/2016/01/20/mobile-wpo-pageshow-async/">无线性能优化：页面可见时间与异步加载</a>
2. <a href="https://developer.mozilla.org/zh-CN/docs/Web/Events/DOMContentLoaded">DOMContentLoaded</a></p>

<h3 id="html部分">HTML部分</h3>

<p>HTML 优化可以分成前端和后端两种</p>

<h4 id="javascript优化">javascript优化</h4>

<ul>
<li>尽量 JAVASCRIPT 脚本放在底部，以防止 JAVASCRIPT 阻塞。</li>
<li>使用defer方式加载 JAVASCRIPT 脚本。</li>
<li>使用aysnc方式加载 JAVASCRIPT 脚本。</li>
<li>避免空的src和href。当link标签的href属性为空、script标签的src属性为空的时候，浏览器渲染的时候会把当前页面的URL作为它们的属性值，从而把页面的内容加载进来作为它们的值，（包括 CSS）。</li>
<li>利用预加载优化资源</li>
<li>加快JavaScript装入速度工具 <a href="https://github.com/getify/LABjs">Lab.js</a></li>
<li>使用适当的CDN：</li>
<li>gulp-uglify压缩js代码</li>
<li>选择性的合并js文件，根据情况，常用的库可以不作合并，有利于缓存。</li>
<li>JavaScript打包成PNG文件，以在不缩小数据的情况下，多压缩35%左右。而且是无损压缩，对比较庞大的脚本来说，在图片指向画布、读取像素的过程中，你会觉得有“一段”装入时间。<a href="http://pngdrive.devinhaus.com/">pngdrive</a>. 开启<code>gzip</code>之后与压缩png相差不大，这个方法有待观察！</li>
<li>dns-prefetch</li>
<li>HTML5 prefetch （兼容性）.预加载图片，网页，不能跨域。</li>
<li>Subresources 表示当前页面必须加载的资源，应该放到页面最顶端先加载，有最高的优先级</li>
<li>prerender chrome</li>
<li>preload</li>
</ul>

<p>较优实践：</p>

<ol>
<li>将js文件放在body最下面，同时使用defer（一般情况下，与js放在body最下效果一样），预下载，在js没有先后执行顺序的情况下使用<code>aysnc</code>，异步加载，并且可将异步加载的文件放在header中。aysnc defer可同时使用，优先执行aysnc</li>
</ol>

<pre><code class="language-js">&lt;script aysnc defer src=&quot;app.js&quot; &gt;&lt;/script&gt;
</code></pre>

<p>gulp压缩代码：</p>

<pre><code class="language-js">var uglify = require('gulp-uglify');

gulp.src('**.js')
.pipe(uglify())
.pipe(gulp.dest('dist'))
</code></pre>

<ol>
<li><p>不使用jquery
使用<a href="http://youmightnotneedjquery.com/">youmightnotneedjquery.com</a>代替<code>jquery</code>，使用一段时间后可以根据使用情况封装常用的函数库。</p>

<p><a href="https://github.com/oneuijs/You-Dont-Need-jQuery/blob/master/README.zh-CN.md">you don｀t need jquery</a></p>

<p><a href="http://youmightnotneedjqueryplugins.com/">plugins</a></p>

<p><a href="https://github.com/madrobby/zepto">Zepto.js</a> api与jquery一样，但是小了很多,为了移动端开发的。</p></li>
</ol>

<h4 id="css优化">CSS优化</h4>

<p>CSS文件：</p>

<ul>
<li>将CSS文件放在header中，</li>
<li>合理架构，使DOM结构尽量简单。避免选择弃超过4个层级。</li>
<li>尽量避免CSS表达式和滤镜。</li>
</ul>

<p>较优实践：
* 使用gulp压缩css。
* 并且使用<code>autoprefixer</code>自动添加浏览器前缀。
* 使用cssMerger合并css文件。</p>

<pre><code class="language-js">var minifyCss = require('gulp-minify-css');
var autoprefixer = require('autoprefixer');

// 从html中筛选出css文件进行合并。
var cssMerger = require('gulp-css-merger');


gulp.src('**.css')
.pipe(autoprefixer)
.pipe(gulp.dest('dist'))
.on('end', function(e){
	gulp.src('dist/**.html')
	.pipe(cssMerger())
	.dist('dist')
})
</code></pre>

<h4 id="图片优化">图片优化</h4>

<ol>
<li>图片压缩</li>
</ol>

<p>工具：</p>

<p><a href="http://www.jpegmini.com/">JPEGMini</a> 十分好用的jpg压缩软件，有破解版！</p>

<p><a href="https://github.com/kyleduo/TinyPNG4Mac">TinyPNG4Mac</a></p>

<p><a href="https://github.com/targetkiller/gulp-imageisux">腾讯智图</a></p>

<ol>
<li>图片合并， css精灵</li>
</ol>

<p><a href="http://imatlas.com/posts/ispriter-update/">ispriter</a></p>

<h3 id="服务端的简单优化">服务端的简单优化</h3>

<ul>
<li>避免404。</li>
<li>删除重复的JAVASCRIPT和CSS。</li>
<li>ETags配置Entity标签。</li>
<li>权衡DNS查找次数</li>
<li>通过Keep-alive机制减少TCP连接。</li>
<li>通过CDN减少延时。</li>
<li>通过合并文件或者Image Sprites减少HTTP请求。</li>
<li>减少重定向（ HTTP 301和40x/50x）。</li>
<li>设置Cache-Control和Expires头</li>
<li>gzip</li>
</ul>

<h3 id="域名收敛">域名收敛</h3>

<p>在移动网络环境下，减少非必要 DNS 请求，将相关域名收敛成一个，可以尝到缓存的红利，进而可以减少打开页面时间</p>

<p>移动端减少 DNS 解析时间有两种方式：
 * 减少 DNS 请求
 * 缩短 DNS 解析路径</p>

<h4 id="域名结构">域名结构</h4>

<p>你也许已经知道域名的结构（或者叫命名空间）是一个树状结构，有树就得有根，这个根是一个‘.’（dot）
说到树状结构其实和我们经常打交道的 *nix 下的目录结构是一个道理。</p>

<pre><code>mkdir -p root/{com,net,org}/{taobao,tmall}/{www,detail}

[22:53:23]    ~ » tree root
root
├── com
│   ├── taobao
│   │   ├── detail
│   │   └── www
│   └── tmall
│       ├── detail
│       └── www
├── net
│   ├── taobao
│   │   ├── detail
│   │   └── www
│   └── tmall
│       ├── detail
│       └── www
└── org
    ├── taobao
    │   ├── detail
    │   └── www
    └── tmall
        ├── detail
        └── www
        
</code></pre>

<p>如上面所示，域名的层级和上面的目录结构是一致的，我们平常对 root 用到的不多，但并不代表它不存在，比如 www.taobao.com 完整的书写形式其实是 www.taobao.com.，对，最后那个点就是根，只不过是浏览器或者系统的解析器自动帮你补全了。</p>

<h4 id="dns-的解析方式">DNS 的解析方式</h4>

<p>上面无形中就说了迭代解析，还有另外一种递归解析方式：只要发出递归查询，服务器必需回答目标 IP 与域名的映射关系。而迭代查询是，服务器收到一次迭代查询回复一次结果，这个结果不一定是目标 IP 与域名的映射关系，也可以是其它 DNS 服务器的地址。</p>

<p>举个栗子，从客户端到本地 DNS 服务器（又称 Local DNS ，就是你电脑里面配置的或者 DHCP 分配的）是属于递归查询，而 DNS 服务器之间就是的交互查询就是迭代查询。</p>

<h4 id="httpdns">HttpDNS</h4>

<p>HttpDNS 是为了解决移动端 DNS 解析请求而生的，顺便解决 DNS 劫持，合并请求和缓存结果进而提高解析质量。</p>

<h5 id="设置过期时间">设置过期时间</h5>

<p>nginx 设置expires</p>

<pre><code class="language-js">    location / {
        root   /var/www;
        index  index.html index.htm;
        expires      30d;
    }
</code></pre>

<h4 id="开启gzip">开启gzip</h4>

<p>nginx 设置gzip</p>

<pre><code class="language-js">    gzip  on;
    # 启用gzip压缩的最小文件，小于设置值的文件将不会压缩
    gzip_min_length 1k;

    # gzip 压缩级别，1-10，数字越大压缩的越好，也越占用CPU时间，后面会有详细说明
     gzip_comp_level 2;

    # 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到。
    gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php;

    # 是否在http header中添加Vary: Accept-Encoding，建议开启
    gzip_vary on;
</code></pre>

<p>有待补充
未完待续</p>

  </div>
</div>
<div class="container has-text-centered">
    
</div>

<div class="container has-text-centered">
  
</div>
<section class="section">
  <div class="container has-text-centered">
    <p></p>
  </div>
</section>


